#include "particle.h"
#include "utility.h"
#include <string.h>
#include <iostream>
#include <sstream>
#include "SDL_gfx/SDL_rotozoom.h"
#include "SDL_gfx/SDL_gfxPrimitives.h"
#include "game_engine.h"
#include "resource_manager.h"

void SetupParticle(PD_Particle* particle, double x, double y, double velx, double vely, double accx, double accy, double lifeSpan, SDL_Surface* image)
{
	if (particle != NULL)
	{
		particle->x = x;
		particle->y = y;
		particle->velx = velx;
		particle->vely = vely;
		particle->accx = accx;
		particle->accy = accy;
		particle->image = image;
		particle->alive = true;
		particle->lifeSpan = lifeSpan;
		particle->frame = 0;
	}
}

ParticleSystem::ParticleSystem(std::string id) : RenderTarget(std::string("psys_") + id)
{
	//Add some default tags
	getIdComp()->addTag("_psys");

    //renderTo.x = 400.0;
    //renderTo.y = 400.0;
	life = 5.0;
	liferange = 3.0;
	maxParticles = 10;
    gravityConstant = true;
    gravityDir.x = 0.0;
    gravityDir.y = 0.0;
    accx = accy = velx = vely = 0.0;
    accxrange = accyrange = velxrange = velyrange = 0.0;
    gravityWellConstant = 0.0;
    
	relativeParticles = false;
	active = true;
	myLife = 0.0;
	myLifeSpan = 1.0;
	removeAfterLifeSpan = false;
    renderCount = 0;
    systemLife = -1;
    systemLifeRange = 0.0;
	coolDown = false;
}

void ParticleSystem::initParticle(PD_Particle* particle)
{
	double px, py, vx, vy, l;
	SDL_Surface* image = NULL;
	px = rand_double_range(-0.5 * xrange, 0.5 * xrange);
	py = rand_double_range(-0.5 * yrange, 0.5 * yrange);
	
	if (!relativeParticles) {
		Sint16 sx, sy;
		getScreenCoords(sx, sy);
		px += sx;
		py += sy;
	}
	
	vx = rand_double_range(velx - 0.5 * velxrange, velx + 0.5 * velxrange);
	vy = rand_double_range(vely - 0.5 * velyrange, vely + 0.5 * velyrange);
	l = rand_double_range(life - 0.5 * liferange, life + 0.5 * liferange);
	if (images.size() > 0)
		image = images[0];
	SetupParticle(particle, px, py, vx, vy, gravityDir.x, gravityDir.y, l, image);
	particle->frame = 0;
	particle->life = 0.0;
	particle->alive = true;
}

double ParticleSystem::getSystemLife() { return myLife; }

void ParticleSystem::addImage(std::string imageName, SDL_Surface* image)
{
	ResourceManager* res = GameEngine::getResources();

	if (image != NULL)
	{
		res->SetImage(imageName, image);
		images.push_back(image);
	}
}

void ParticleSystem::SetupFireTemplate()
{
	SDL_Surface *tempImage = NULL, *part_image = NULL;

	ResourceManager* res = GameEngine::getResources();

	//Clear particle images
	images.clear();

	// Justin's spark sprite tests
	tempImage = load_image("10x12 spark.png", true);
	part_image = res->GetImage("spark-1", true);
	if (part_image != NULL)
		images.push_back(part_image);
	else
		addImage("spark-1", zoomSurface(tempImage, 1.0, 1.0, 0));
	SDL_FreeSurface(tempImage);
	
	tempImage = load_image("5x6 spark.png", true);
	part_image = res->GetImage("spark-2", true);
	if (part_image != NULL)
		images.push_back(part_image);
	else
		addImage("spark-2", zoomSurface(tempImage, 1.0, 1.0, 0));
	SDL_FreeSurface(tempImage);

	tempImage = load_image("3x3 spark.png", true);
	part_image = res->GetImage("spark-3", true);
	if (part_image != NULL)
		images.push_back(part_image);
	else
		addImage("spark-3", zoomSurface(tempImage, 1.0, 1.0, 0));
	SDL_FreeSurface(tempImage);

	tempImage = load_image("smoke-32.png", true);
	
	part_image = res->GetImage("smoke-1", true);
	if (part_image != NULL)
		images.push_back(part_image);
	else
		addImage("smoke-1", zoomSurface(tempImage, 0.25, 0.25, 0));
	
	part_image = res->GetImage("smoke-2", true);
	if (part_image != NULL)
		images.push_back(part_image);
	else
		addImage("smoke-2", zoomSurface(tempImage, 0.5, 0.5, 0));
	/*
	part_image = res->GetImage("smoke-3", true);
	if (part_image != NULL)
		images.push_back(part_image);
	else
		addImage("smoke-3", zoomSurface(tempImage, 0.8, 0.8, 0));
	part_image = res->GetImage("smoke-4", true);
	if (part_image != NULL)
		images.push_back(part_image);
	else
		addImage("smoke-4", zoomSurface(tempImage, 1.2, 1.2, 0));
	*/
	SDL_FreeSurface(tempImage);
}

void ParticleSystem::SetupSwarmTemplate()
{
	SDL_Surface *tempImage = NULL, *part_image = NULL;

	ResourceManager* res = GameEngine::getResources();

	//Clear particle images
	images.clear();

    tempImage = res->GetImage("spark_particle_1.png", true);
    
    images.push_back(tempImage);
	part_image = res->GetImage("spark-sm-1", true);
	if (part_image != NULL)
		images.push_back(part_image);
	else
        addImage("spark-sm-1", shrinkSurface(tempImage, 2, 2));
	part_image = res->GetImage("spark-sm-2", true);
	if (part_image != NULL)
		images.push_back(part_image);
	else
        addImage("spark-sm-2", shrinkSurface(tempImage, 3, 3));
	part_image = res->GetImage("spark-sm-3", true);
	if (part_image != NULL)
		images.push_back(part_image);
	else
        addImage("spark-sm-3", shrinkSurface(tempImage, 5, 5));
	part_image = res->GetImage("spark-sm-5", true);
	if (part_image != NULL)
		images.push_back(part_image);
	else
        addImage("spark-sm-5", shrinkSurface(tempImage, 10, 10));
	part_image = res->GetImage("spark-sm-10", true);
	if (part_image != NULL)
		images.push_back(part_image);
	else
        addImage("spark-sm-10", shrinkSurface(tempImage, 20, 20));
}

void ParticleSystem::Init()
{
	particles.clear();
	for (int i = 0; i < maxParticles; i++)
	{
		particles.push_back(PD_Particle());
		initParticle(&particles[i]);
	}

    if (systemLife >= 0)
        myLifeSpan = rand_double_range(systemLife - 0.5 * systemLifeRange, systemLife + 0.5 * systemLifeRange);
    else
        myLifeSpan = -1; //Forever
	myLife = 0.0;
	coolDown = false;
}

ParticleSystem::~ParticleSystem()
{
    //std::stringstream logLine;
    //logLine << "~ParticleSystem(): id:" << getIdComp()->id << " renderCount:" << renderCount;
	//log_info(logLine.str());
}

void ParticleSystem::StartSystem()
{
	active = false;
}

void ParticleSystem::StopSystem()
{
	active = true;
}

void ParticleSystem::ToggleSystem()
{
	active = !active;
}

void ParticleSystem::ClearSystem()
{
	for (int i=0; i < particles.size(); i++)
	{
		particles[i].alive = false;
	}
}

void ParticleSystem::Update(double timeDelta)
{
	SDL_Surface* screen = GameEngine::getInstance()->getScreen();
	Sint16 aliveCount = 0;

	if (active)
    {
		myLife += timeDelta;
		if (myLifeSpan > 0 && myLife >= myLifeSpan)
		{
			coolDown = true;
		}

        for (int i = 0; i < particles.size(); i++)
        {
            //Re-spawn dead particles as new ones
            if (!particles[i].alive)
            {
				if (!coolDown)
					initParticle(&particles[i]);
				else
					continue;
            }
			
			if (particles[i].alive)
			{
				aliveCount++;
				//Calculate gravity to gravity well if not constant
				if (!gravityConstant)
				{
					double dx = particles[i].x - gravityWell.x;
					double dy = particles[i].y - gravityWell.y;
					if (relativeParticles) {
						dx += renderTo.x;
						dy += renderTo.y;
					}

					if (abs(dx) > 10.0)
					{

						double mult = 0.0;
						if (abs(dx) > 300)
							mult = 0.2 / abs(dx);
						else if (abs(dx) < 50)
							mult = 1.0 / abs(dx);
						else
							mult = (300.0 - abs(dx)) / 250.0;
						if (abs(dx) > 50.0)
							dx = -dx;
						else
							dx *= 2.0;
						particles[i].accx = dx * gravityWellConstant;
					}
					else
						particles[i].accx = 0.0;
					if (abs(dy) > 10.0)
					{
						double mult = 0.0;
						if (abs(dy) > 300)
							mult = 0.2 / abs(dy);
						else if (abs(dy) < 50)
							mult = 1.0 / abs(dy);
						else
							mult = (300.0 - abs(dy)) / 250.0;
						if (abs(dy) > 50.0)
							dy = -dy;
						else
							dy *= 2.0;
						particles[i].accy = dy * gravityWellConstant;
					}
					else
						particles[i].accy = 0.0;
				}

				//Apply acceleration to velocity
				particles[i].velx += particles[i].accx * timeDelta;
				particles[i].vely += particles[i].accy * timeDelta;

				//Apply velocity to position
				particles[i].x += particles[i].velx * timeDelta;
				particles[i].y += particles[i].vely * timeDelta;

				if (abs(particles[i].velx) > PARTICLE_MAX_SPEED)
					particles[i].velx *= PARTICLE_MAX_SPEED / abs(particles[i].velx);
				if (abs(particles[i].vely) > PARTICLE_MAX_SPEED)
					particles[i].vely *= PARTICLE_MAX_SPEED / abs(particles[i].vely);

				//Check boundary-kill conditions
				if (relativeParticles)
				{
					if (particles[i].x < -500.0 || particles[i].x > 500.0 ||
						particles[i].y < -500.0 || particles[i].y > 500.0)
						particles[i].life = particles[i].lifeSpan;
				}
				else
				{

					//if (particles[i].x < -20.0 || particles[i].x > screen->w + 20.0 ||
					//    particles[i].y < -20.0 || particles[i].y > screen->h + 20.0)
					//    particles[i].life = particles[i].lifeSpan;
				}


				//Check lifespan contraints and update image to smaller ones if available
				if (particles[i].alive)
				{
					particles[i].life += timeDelta;
					if (particles[i].life >= particles[i].lifeSpan)
						particles[i].alive = false;
					else if (images.size() > 0) {
						double threshold = (double)(particles[i].frame + 1) / (double)images.size(); //threshold for getting to next scale
						double current = particles[i].life / particles[i].lifeSpan;

						if (current >= threshold)
						{
							particles[i].frame += 1;
							particles[i].image = images[particles[i].frame];
						}
					}
				}
			}
        }
		//If no particle is alive, stop updating. No point until there is an alive particle. Calling Init() will re-initialize the particle array, making them all alive; setting active is also necessary.
		if (aliveCount == 0)
		{
			active = false;

			if (removeAfterLifeSpan)
			{
				/* //Causes bug, need to work out
				if (parent != NULL)
				{
					parent->removeChild(this);
					delete this;
					return;
				}
				*/
			}
		}
    }
    
	// Call default functionality (update children)
    RenderTarget::Update(timeDelta);
}

void ParticleSystem::Render(SDL_Surface* screen)
{
	if (visible)
		renderParticles(screen);
    
    // Call default functionality (render children)
    RenderTarget::Render(screen);
}

void ParticleSystem::renderParticles(SDL_Surface* screen)
{
	renderCount++;
    for (int i = 0; i < particles.size(); i++)
	{
		if (particles[i].alive && particles[i].image != NULL)
		{
			Sint16 rx, ry;
			calculateScreenCoords(&particles[i], rx, ry, true);
            
			apply_surface(rx, ry, particles[i].image, screen);
		}
	}
}

void ParticleSystem::calculateScreenCoords(PD_Particle *particle, Sint16 &rx, Sint16 &ry, bool useImageSize)
{
	//Get my screen coords
	Sint16 px, py;
	getScreenCoords(px, py);

	if (particle != NULL)
	{
		if (useImageSize && particle->image != NULL) {
			rx = (Sint16)particle->x - (particle->image->w / 2.0);
			ry = (Sint16)particle->y - (particle->image->h / 2.0);
		}
		else {
			rx = (Sint16)particle->x;
			ry = (Sint16)particle->y;
		}
		if (relativeParticles)
		{
			rx += px;
			ry += py;
		}
	}
}

TraceParticleSystem::TraceParticleSystem(std::string id) : ParticleSystem(id)
{
	renderImages = false;
	renderImagesBelow = false;
}

void TraceParticleSystem::Init()
{
	ParticleSystem::Init();

	for (int i = 0; i < particles.size(); i++)
	{
		particleTraces.push_back(PD_Particle());
		particleTraces[i].x = particles[i].x;
		particleTraces[i].y = particles[i].y;
	}

}

void TraceParticleSystem::Update(double deltaTime)
{
	if (!active)
		return;

	//Save old coordinates for trace
	for (int i = 0; i < particles.size() && i < particleTraces.size(); i++)
	{
		particleTraces[i].x = particles[i].x;
		particleTraces[i].y = particles[i].y;
		particleTraces[i].life = particles[i].life;
	}

	ParticleSystem::Update(deltaTime);

	for (int i = 0; i < particles.size() && i < particleTraces.size(); i++)
	{
		//If updated life, less than old life, there was a reset, so update coordinates of trace
		if (particles[i].life < particleTraces[i].life)
		{
			particleTraces[i].x = particles[i].x;
			particleTraces[i].y = particles[i].y;
			particleTraces[i].life = particles[i].life;
		}
	}
}

void TraceParticleSystem::Render(SDL_Surface* screen)
{
	if (renderImagesBelow && renderImages)
		renderParticles(screen);

	for (int i = 0; i < particles.size(); i++)
	{
		if (particles[i].alive && particles[i].image != NULL)
		{
			Sint16 rx, ry, orx, ory;
			calculateScreenCoords(&particles[i], rx, ry, false);
			calculateScreenCoords(&particleTraces[i], orx, ory, false);

			thickLineRGBA(screen, orx, ory, rx, ry, 2, 255, 255, 0, 128); 
		}
	}

	if (!renderImagesBelow && renderImages)
		renderParticles(screen);
    
    RenderTarget::Render(screen); //render children
}